defmodule PlausibleWeb.Live.Verification do
  @moduledoc """
  LiveView coordinating the site verification process.
  Onboarding new sites, renders a standalone component.
  Embedded modal variant is available for general site settings.
  """
  use PlausibleWeb, :live_view

  import PlausibleWeb.Components.Generic

  alias Plausible.InstallationSupport.{State, Verification}

  @component PlausibleWeb.Live.Components.Verification
  @slowdown_for_frequent_checking :timer.seconds(5)

  def mount(
        %{"domain" => domain} = params,
        _session,
        socket
      ) do
    current_user = socket.assigns.current_user

    site =
      Plausible.Sites.get_for_user!(current_user, domain,
        roles: [
          :owner,
          :admin,
          :editor,
          :super_admin,
          :viewer
        ]
      )

    true = Plausible.Sites.regular?(site)

    private = Map.get(socket.private.connect_info, :private, %{})

    super_admin? = Plausible.Auth.is_super_admin?(current_user)
    has_pageviews? = has_pageviews?(site)

    custom_url_input? = params["custom_url"] == "true"

    socket =
      assign(socket,
        url_to_verify: nil,
        site: site,
        super_admin?: super_admin?,
        domain: domain,
        has_pageviews?: has_pageviews?,
        component: @component,
        installation_type: get_installation_type(params, site),
        report_to: self(),
        delay: private[:delay] || 500,
        slowdown: private[:slowdown] || 500,
        flow: params["flow"] || "",
        checks_pid: nil,
        attempts: 0,
        polling_pageviews?: false,
        custom_url_input?: custom_url_input?
      )

    if connected?(socket) and not custom_url_input? do
      launch_delayed(socket)
    end

    {:ok, socket}
  end

  def render(assigns) do
    ~H"""
    <PlausibleWeb.Components.FlowProgress.render flow={@flow} current_step="Verify installation" />
    <.custom_url_form :if={@custom_url_input?} domain={@domain} />
    <.live_component
      :if={not @custom_url_input?}
      module={@component}
      installation_type={@installation_type}
      domain={@domain}
      id="verification-standalone"
      attempts={@attempts}
      flow={@flow}
      awaiting_first_pageview?={not @has_pageviews?}
      super_admin?={@super_admin?}
    />
    """
  end

  def handle_event("launch-verification", _, socket) do
    launch_delayed(socket)
    {:noreply, reset_component(socket)}
  end

  def handle_event("retry", _, socket) do
    launch_delayed(socket)
    {:noreply, reset_component(socket)}
  end

  def handle_event("verify-custom-url", %{"custom_url" => custom_url}, socket) do
    socket =
      socket
      |> assign(url_to_verify: custom_url)
      |> assign(custom_url_input?: false)

    launch_delayed(socket)
    {:noreply, reset_component(socket)}
  end

  def handle_info({:start, report_to}, socket) do
    domain = socket.assigns.domain
    checks_pid = socket.assigns.checks_pid

    if is_pid(checks_pid) and Process.alive?(checks_pid) do
      {:noreply, socket}
    else
      case Plausible.RateLimit.check_rate(
             "site_verification:#{domain}",
             :timer.minutes(60),
             3
           ) do
        {:allow, _} -> :ok
        {:deny, _} -> :timer.sleep(@slowdown_for_frequent_checking)
      end

      {:ok, pid} =
        Verification.Checks.run(
          socket.assigns.url_to_verify,
          domain,
          socket.assigns.installation_type,
          report_to: report_to,
          slowdown: socket.assigns.slowdown
        )

      {:noreply, assign(socket, checks_pid: pid, attempts: socket.assigns.attempts + 1)}
    end
  end

  def handle_info({:check_start, {check, state}}, socket) do
    to_update = [message: check.report_progress_as()]

    to_update =
      if is_binary(state.url) do
        Keyword.put(to_update, :url_to_verify, state.url)
      else
        to_update
      end

    update_component(socket, to_update)

    {:noreply, socket}
  end

  def handle_info({:all_checks_done, %State{} = state}, socket) do
    interpretation = Verification.Checks.interpret_diagnostics(state)

    if not socket.assigns.has_pageviews? do
      schedule_pageviews_check(socket)
    end

    update_component(socket,
      finished?: true,
      success?: interpretation.ok?,
      interpretation: interpretation,
      verification_state: state
    )

    {:noreply, assign(socket, checks_pid: nil)}
  end

  def handle_info(:check_pageviews, socket) do
    socket =
      if has_pageviews?(socket.assigns.site) do
        redirect_to_stats(socket)
      else
        socket
        |> assign(polling_pageviews?: false)
        |> schedule_pageviews_check()
      end

    {:noreply, socket}
  end

  @supported_installation_types_atoms PlausibleWeb.Tracker.supported_installation_types()
                                      |> Enum.map(&String.to_atom/1)
  defp get_installation_type(params, site) do
    cond do
      params["installation_type"] in PlausibleWeb.Tracker.supported_installation_types() ->
        params["installation_type"]

      (saved_installation_type = get_saved_installation_type(site)) in @supported_installation_types_atoms ->
        Atom.to_string(saved_installation_type)

      true ->
        PlausibleWeb.Tracker.fallback_installation_type()
    end
  end

  defp get_saved_installation_type(site) do
    case PlausibleWeb.Tracker.get_tracker_script_configuration(site) do
      %{installation_type: installation_type} ->
        installation_type

      _ ->
        nil
    end
  end

  defp schedule_pageviews_check(socket) do
    if socket.assigns.polling_pageviews? do
      socket
    else
      Process.send_after(self(), :check_pageviews, socket.assigns.delay * 2)
      assign(socket, polling_pageviews?: true)
    end
  end

  defp redirect_to_stats(socket) do
    stats_url = Routes.stats_path(PlausibleWeb.Endpoint, :stats, socket.assigns.domain, [])
    redirect(socket, to: stats_url)
  end

  defp reset_component(socket) do
    update_component(socket,
      message: "We're visiting your site to ensure that everything is working",
      finished?: false,
      success?: false,
      diagnostics: nil
    )

    socket
  end

  defp update_component(_socket, updates) do
    send_update(
      @component,
      Keyword.merge(updates, id: "verification-standalone")
    )
  end

  defp launch_delayed(socket) do
    Process.send_after(self(), {:start, socket.assigns.report_to}, socket.assigns.delay)
  end

  defp has_pageviews?(site) do
    Plausible.Stats.Clickhouse.has_pageviews?(site)
  end

  defp custom_url_form(assigns) do
    ~H"""
    <.focus_box>
      <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900">
        <Heroicons.globe_alt class="h-6 w-6 text-blue-600 dark:text-blue-200" />
      </div>
      <div class="mt-8">
        <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">
          Enter Your Custom URL
        </h3>
        <p class="text-sm mt-4 text-gray-600 dark:text-gray-400">
          Please enter the URL where your website with the Plausible script is located.
        </p>
        <form phx-submit="verify-custom-url" class="mt-6">
          <div class="mb-4">
            <label
              for="custom_url"
              class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
            >
              Website URL
            </label>
            <input
              type="url"
              name="custom_url"
              id="custom_url"
              required
              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-xs focus:outline-hidden focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:text-white"
              placeholder={"https://#{@domain}"}
              value={"https://#{@domain}"}
            />
          </div>
          <button
            type="submit"
            class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-xs text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-hidden focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-600"
          >
            Verify Installation
          </button>
        </form>
      </div>
    </.focus_box>
    """
  end
end
